#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>

#include <unistd.h>
#include <string.h>
#include <sys/soundcard.h>
#include <sys/poll.h>
#include "include/sound.h"

#include "../../../../uClinux-dist/linux-2.4.x/include/asm/arch/nuc700_i2c.h"
#include "../../../../uClinux-dist/linux-2.4.x/include/asm/arch/nuc700_audio_regs.h"

#define I2C_WM8510_ADDR		0x34
#define WM8510_REGCON_LEN 	sizeof(WM8510_REG)/4

#define BUFFER_SIZE	(2 * 1024 * 1024)
char buffer[BUFFER_SIZE/32768][32768];
int block_size = 4096;

int choice;
char *dsp_device[] = {"/dev/dsp0", "/dev/dsp1"};
char *mixer_device[] = {"/dev/mixer0", "/dev/mixer1"};
char *interface_type[] ={"IIS", "AC97"};

int Sound_status = SOUND_OFF;

struct sound_list sl;

const unsigned int WM8510_REG[] = {
			0x00000000,		// Reset WM8510
			//0X00310006,	// Set VMID = 1.5 x AVDD/2
			0x0001010E,		// Power Manage't 1: BIASEN, BUFIOEN, VMIDSEL= 10
			//0x0001005E,	//		     MIC2EN, MICBEN
			//0x00020015,	// Power Manage't 2: BOOSTEN, INPPGAEN, ADCEN
			//0x00030001,		// Power Manage't 3: DACEN
			//0x00030005,		//		     SPKMIXEN
			0x00040014,		// Audio Interface: FMT= 10(I2S format), 16Bit, Data appear in 'right'
			//0x00050000,	// Companding ctrl: Enable loopback set 0x0001
			0x00060000,		// Clock Gen ctrl:
			//0x0007000B,	// Additional ctrl: 8k	Sample frequence
			0x00070001,
			//0x00080001,	// GPIO Stuff:
			0x000A002C,		// DAC Control: De-Enphasis Control set 44.1kHz sample rate
								//					DAC oversample rate select: set 128x(best SNR)
			0x000B00FF,		// DAC Digital Vol: set Maxium Volume
			//0x000E0108,	// ADC Control:
			//0x000F00FF,	// ADC Digital Vol:
			0x00180144,		// DAC Limiter 1:
			0x00190033,		// DAC Limiter 2:
			//0x001B0000,	// Notch Filter1:
			//0x001C0000,	// Notch Filter2:
			//0x001D0000,	// Notch Filter3:
			//0x001E0000,	// Notch Filter4:
			//0x00200038,	// ALC control 1:
			//0x0021000B,	// ALC control 2:
			//0x00220032,	// ALC control 3:
			//0x00230000,	// Noise Gate:
			//0x00240008,	// PLL N
			//0x0025000C,	// PLL K 1
			//0x00260093,	// PLL K 2
			//0x002700E9,	// PLL K 3
			//0x00280000,	// Attenuation ctrl
			//0x002C0003,	// Input ctrl
			//0x002D0038,	// INP PGA gain ctrl
			//0x002F0107,	// ADC Boost ctrl
			0x00310006,	// Output ctrl: SKPBOOST
			0x00320001,	// SPK mixer ctrl slect DAC to Speaker
			0x0036003F,	// SPK volume ctrl
			//0x00380001,	// MONO mixer ctrl
			0x00030065	// SPKNEN & SPKPEN is set Enable( SPKMIXEN & DACEN set on front)
			};

int WM8510_Initial(void)
{
	int fd;
	int index;
	int audio_i2c_addr = I2C_WM8510_ADDR >> 1;
	unsigned int reg_addr;
	unsigned char wm_buf[4] = {0,0,0,0};
	volatile unsigned int data;
	unsigned int dump;
	struct sub_address addr;

	printf("***Initial WM8510 *****\n");

	fd = open("/dev/i2c0", O_RDWR);
	if( fd < 0 ){
		printf("Open /dev/i2c0 error!\n");
		return -1;
	}
	//printf("Set WM8510 I2C ADDRESS: %x \n", audio_i2c_addr);
	ioctl(fd, I2C_IOC_SET_DEV_ADDRESS, audio_i2c_addr);
	//printf("Set I2C Speed 100K\n");
	ioctl(fd, I2C_IOC_SET_SPEED, 100);
	addr.sub_addr_len = 1;
	
	for(index = 0; index < WM8510_REGCON_LEN; index++)
	{	
		reg_addr = (WM8510_REG[index] & 0xFFFF0000) >> 15;
		if(WM8510_REG[index] & 0x00000100)
		{
			reg_addr |= 0x00000001;
		}
		addr.sub_addr = reg_addr;
		ioctl(fd, I2C_IOC_SET_SUB_ADDRESS, &addr);
//printf("Write I2C sub addr\n"); 
		data = WM8510_REG[index] & 0x000000FF;
//printf("Get Command: 0x%x", data);
		wm_buf[0] = (unsigned char)data;
//printf("Write WM8510 REG [%02x]: 0x%08x, 0x%02x \n", addr.sub_addr, WM8510_REG[index], data);
		if(write(fd, wm_buf, 1) <= 0) return -1;
		//for(dump = 0; dump < 100; dump++);
	}
	
	close(fd);
	return 0;
}

int WM8510_Power_On(void)
{
	int fd;
	int index;
	int audio_i2c_addr = I2C_WM8510_ADDR >> 1;
	unsigned int reg_addr;
	unsigned char wm_buf[4] = {0,0,0,0};
	volatile unsigned int data;
	unsigned int dump;
	unsigned int PowerOffCmd = 0x00030065;
	struct sub_address addr;

	//printf("***WM8510 Power On*****\n");

	fd = open("/dev/i2c0", O_RDWR);
	if( fd < 0 ){
		printf("Open /dev/i2c0 error!\n");
		return -1;
	}
	ioctl(fd, I2C_IOC_SET_DEV_ADDRESS, audio_i2c_addr);
	ioctl(fd, I2C_IOC_SET_SPEED, 100);
	addr.sub_addr_len = 1;
	
	reg_addr = (PowerOffCmd & 0xFFFF0000) >> 15;
	if(PowerOffCmd & 0x00000100)
	{
		reg_addr |= 0x00000001;
	}
	addr.sub_addr = reg_addr;

	ioctl(fd, I2C_IOC_SET_SUB_ADDRESS, &addr);

	data = PowerOffCmd & 0x000000FF;

	wm_buf[0] = (unsigned char)data;

	if(write(fd, wm_buf, 1) <= 0) return -1;

	close(fd);

	return 0;
}

int WM8510_Power_Save(void)
{
	int fd;
	int index;
	int audio_i2c_addr = I2C_WM8510_ADDR >> 1;
	unsigned int reg_addr;
	unsigned char wm_buf[4] = {0,0,0,0};
	volatile unsigned int data;
	unsigned int dump;
	unsigned int PowerOffCmd = 0x00030000;
	struct sub_address addr;

	//printf("***WM8510 Power Save*****\n");

	fd = open("/dev/i2c0", O_RDWR);
	if( fd < 0 ){
		printf("Open /dev/i2c0 error!\n");
		return -1;
	}
	ioctl(fd, I2C_IOC_SET_DEV_ADDRESS, audio_i2c_addr);
	ioctl(fd, I2C_IOC_SET_SPEED, 100);
	addr.sub_addr_len = 1;
	
	reg_addr = (PowerOffCmd & 0xFFFF0000) >> 15;
	if(PowerOffCmd & 0x00000100)
	{
		reg_addr |= 0x00000001;
	}
	addr.sub_addr = reg_addr;

	ioctl(fd, I2C_IOC_SET_SUB_ADDRESS, &addr);

	data = PowerOffCmd & 0x000000FF;

	wm_buf[0] = (unsigned char)data;

	if(write(fd, wm_buf, 1) <= 0) return -1;

	close(fd);

	return 0;
}

int Play_List(int dsp, int index)
{
	const char *pcmfiles[] = {
		"/tmp/ftp/system/pcm/Acc.pcm",
		"/tmp/ftp/system/pcm/Brk.pcm",
		"/tmp/ftp/system/pcm/Engineoff.pcm",
		"/tmp/ftp/system/pcm/Engineon.pcm",
		"/tmp/ftp/system/pcm/Link.pcm",
		"/tmp/ftp/system/pcm/Newdata.pcm",
		"/tmp/ftp/system/pcm/Restart.pcm",
		"/tmp/ftp/system/pcm/Sleep.pcm",
		"/tmp/ftp/system/pcm/Start.pcm",
		"/tmp/ftp/system/pcm/Timesync.pcm",
		"/tmp/ftp/system/pcm/Wifiin.pcm",
		"/tmp/ftp/system/pcm/Wifioff.pcm",
		"/tmp/ftp/system/pcm/Wifion.pcm"
	};
	int samplerate = 22050;
	int fd, status, ret = -1, channel = 2;
	int i= 0;
	int block_count = 0;

	//printf("SampleRate %d Stereo Test ...  \n", samplerate[i]);
	fd = open(pcmfiles[index], O_RDONLY);
	if ( fd < 0){
		printf("SKIP ( Open file %s error )\n", pcmfiles[index]);
		return ret;
	}

	ret = 0;
	//printf("Playing Sound %s\n", pcmfiles[index]);
	printf("%s,%d\n",pcmfiles[index],samplerate);
	ioctl(dsp, SNDCTL_DSP_SPEED, &samplerate);
	ioctl(dsp, SNDCTL_DSP_CHANNELS, &channel);
	while(read(fd, buffer[block_count++], block_size));
	//printf("Block count of Sound File is %d (block_size: %d)\n", block_count, block_size);
	for(i = 0; i < block_count; i++)
		write(dsp, buffer[i], block_size);

	close(fd);

	/* wait the last block play off */
	ioctl(dsp, SNDCTL_DSP_SYNC);		
	ioctl(dsp, SNDCTL_DSP_RESET);	

	return ret;

}

/*将需要播放的音频文件序号存入播放缓存*/
void saveSoundList(struct sound_list *sl, int index, int status)
{
	if(status==SOUND_OFF) return;
	pthread_mutex_lock(&sl->lock);
	/* Wait until buffer is not full */
	while ((sl->writepos + 1) % SL_BUF_SIZE == sl->readpos) {
		printf("put_cardata_wait_condition!\n");
	   pthread_cond_wait(&sl->notfull, &sl->lock);
   /* pthread_cond_wait reacquired c->lock before returning */
	}
  /* Write the data and advance write pointer */
	sl->list[(sl->writepos * SL_DATA_LEN)] = index;
	sl->writepos++;
  	if (sl->writepos >= SL_BUF_SIZE) sl->writepos = 0;
  /* Signal that the buffer is now not empty */
  pthread_cond_signal(&sl->notempty);
  pthread_mutex_unlock(&sl->lock);
}

/*从待播放清单中读出*/
void readSoundList(struct sound_list *sl, int *index)
{
	pthread_mutex_lock(&sl->lock);
	/* Wait until list is not empty */
	while (sl->writepos == sl->readpos) {
		//printf("get_cardata_wait_condition!\n");
		pthread_cond_wait(&sl->notempty, &sl->lock);
  	}
  /* Read the data and advance read pointer */
  *index = sl->list[(sl->readpos * SL_DATA_LEN)];
  sl->readpos++;
  if (sl->readpos >= SL_BUF_SIZE) sl->readpos = 0;
  /* Signal that the buffer is now not full */
  pthread_cond_signal(&sl->notfull);
  pthread_mutex_unlock(&sl->lock);
}

void Init_PlayList(struct sound_list *sl)
{
	/* initial sound list */
	pthread_mutex_init(&sl->lock, NULL);
	sl->writepos = 0;
	sl->readpos = 0;
	pthread_cond_init(&sl->notempty, NULL);
	pthread_cond_init(&sl->notfull, NULL);
}

int ioctl_test(int dsp, int mixer)
{
	int data;
	mixer_info info;
	char tmpBuf[40];

	printf("***IOCTL Test ***\n");

	ioctl(dsp, OSS_GETVERSION, &data);
	if(data != SOUND_VERSION) {
		printf("Get OSS_GETVERSION error, %d\n", data);
		return -1;
	}

	ioctl(mixer, OSS_GETVERSION, &data);
	if(data != SOUND_VERSION) {
		printf("Get OSS_GETVERSION error, %d\n", data);
		return -1;
	}

	ioctl(dsp, SNDCTL_DSP_GETCAPS, &data);

	/* set volume to 100 */
	data = 0x6464;
	ioctl(mixer, MIXER_WRITE(SOUND_MIXER_VOLUME), &data);

	ioctl(mixer, SOUND_MIXER_INFO, &info);
	memset(tmpBuf, 0, sizeof(tmpBuf));
	strncpy(tmpBuf, info.name, 32);
	printf("Device Information : %s\n", tmpBuf);

	/* get internel data block size */
	ioctl(dsp, SNDCTL_DSP_GETBLKSIZE, &data);
	printf("Internal Fragment Size : %d\n", data);
	block_size = data;


	return 0;
}

void * Start_Sound(void * data)
{
	int dsp = 0, mixer = 0, retval = 0;
	int play_index = 0;
	
	Init_PlayList(&sl);	
	
	if(WM8510_Initial() < 0)
	{
		printf("Initial Failed!\n");
		return;
	}
	else
		printf("WM8510 Initial Reg Num: %d\n", WM8510_REGCON_LEN);

	printf("Open device with IIS Port\n");
	choice = 0; 
	dsp = open(dsp_device[choice], O_RDWR);
	if( dsp < 0 ){
		printf("Open device %s error\n", dsp_device[choice]);
		return;
	}

	mixer = open(mixer_device[choice], O_RDWR);
	if ( mixer < 0){
		printf("Open %s error\n", mixer_device[choice]);
		return;
	}

	retval = ioctl_test(dsp, mixer);
	printf("***IOCTRL Test ... %s\n", retval?"Failed":"Pass");

	while(1){
		readSoundList(&sl, &play_index);
		WM8510_Power_On();
		Play_List(dsp, play_index);
		WM8510_Power_Save();
	}
}
